package yxy.apple.util;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

/** 发布订阅模式 */
public class PubSub {
	/** 发布者 游戏的状态变动通过这个接口发布 */
	public static interface IPublisher {
		public void subscribe(ISubscriber subscriber);

		public void publish(ISubscription subscription);
	}

	/** 订阅者 根据游戏变动进行处理或不处理 */
	public static interface ISubscriber {
		public void receive(ISubscription subscription);
	}

	/** 订阅物 发布者产生的消息 */
	public static interface ISubscription {
	}

	/** 分销器的抽象实现 */
	public static abstract class Distributor {

		private Queue<ISubscriber> queue;

		public Distributor(Queue<ISubscriber> queue) {
			this.queue = queue;
		}

		public Collection<ISubscriber> collection() {
			return this.queue;
		}

		/** 向订阅者进行通知 */
		abstract public void notify(ISubscription subscription);

		public void register(ISubscriber subscriber) {
			queue.add(subscriber);
		}
	}

	/** 基本发布者 */
	abstract public static class Publisher implements IPublisher {

		private Distributor distributor = new Distributor(new LinkedBlockingQueue<ISubscriber>()) {
			public void notify(ISubscription subscription) {
				for (ISubscriber subscriber : collection()) {
					try {
//						System.err.println(subscriber.getClass().getName() + "\t<--\t" + subscription.getClass().getName());
						subscriber.receive(subscription);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		};

		@Override
		public void subscribe(ISubscriber subscriber) {
			distributor.register(subscriber);
		}

		@Override
		public void publish(ISubscription subscription) {
			distributor.notify(subscription);
		}

	}

	/** 通过反射SubscriptionMethod的方法来响应订阅消息的订阅者抽象实现 */
	public static abstract class MethodSubscriber implements ISubscriber {

		public MethodSubscriber() {
			Method[] methods = getClass().getDeclaredMethods();
			for (Method method : methods) {
				if (method.isAnnotationPresent(SubscriptionMethod.class)) {
					SubscriptionMethod subscriptionMethod = method.getAnnotation(SubscriptionMethod.class);
					Class<? extends ISubscription> classType = subscriptionMethod.Type();
					methodMap.put(classType, method);
				}
			}
		}

		private HashMap<Class<? extends ISubscription>, Method> methodMap = new HashMap<>();

		@Override
		public void receive(ISubscription subscription) {
			try {
				Method method = methodMap.get(subscription.getClass());
				if (method == null) {
					return;
				}
				method.setAccessible(true);
				method.invoke(this, subscription);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	@Documented
	@Target({ ElementType.METHOD })
	@Retention(RetentionPolicy.RUNTIME)
	public static @interface SubscriptionMethod {
		Class<? extends ISubscription> Type();
	}
}
